package se.perp.c4.ui;

import se.perp.c4.control.iface.IPieceSelectionListener;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.SwingWorker;
import javax.swing.border.Border;
import se.perp.c4.model.Actor;
import se.perp.c4.model.Actor.EActor;
import se.perp.c4.control.iface.IGamePieceListener;

public abstract class GamePiece extends JComponent
        implements MouseListener, MouseMotionListener {

    private Point clickPoint;
    private boolean wasDragged = false;
    private boolean mouseDown = false;
    private final Color normalBg,  hoverBg,  clickBg,  normalFrame,  normalText,  clickFrame,  clickText,  markBg;
    private Color currTextColor,  currBgColor;
    private final Border normalBorder,  clickBorder;
    private final String text;
    private final Actor.EActor actor;
    private Point lastPos = null;
    private boolean movable = true;
    private boolean isMarked = false;
    private boolean isKnown = true;
    private boolean isHovered = false;
    private boolean selectionMode = false;
    private boolean selectable = false;
    private final Color selectableColor = new Color(0, 255, 0, 96),  selectableColorHover = new Color(0, 255, 0, 192);

    // General mouse interaction listener.
    private IGamePieceListener listener = null;
    // Listens to clicks when in piece selection mode (for Recall to Mordor).
    private IPieceSelectionListener selectionListener = null;

    public GamePiece(
            EActor actor, String text, Color normalBg, Color hoverBg, Color clickBg,
            Color normalFrame, Color normalText, Color clickFrame,
            Color clickText, Color markBg) {
        this.actor = actor;
        this.text = text;
        this.normalBg = normalBg;
        this.hoverBg = hoverBg;
        this.clickBg = clickBg;
        this.normalFrame = normalFrame;
        this.normalText = normalText;
        this.clickFrame = clickFrame;
        this.clickText = clickText;
        this.markBg = markBg;

        currTextColor = normalText;

        addMouseListener(this);
        addMouseMotionListener(this);

        normalBorder = BorderFactory.createLineBorder(normalFrame);
        clickBorder = BorderFactory.createLineBorder(clickFrame);

        setBorder(normalBorder);
        currBgColor = normalBg;
        setFont(FontSet.GAMEPIECE);

        setSize(45, 25);
        remember();
    }

    private BackgroundAnimator currBgAnim = null;
    private class BackgroundAnimator extends SwingWorker<Void, Void> {

        Color c1, c2;

        public BackgroundAnimator(Color c1, Color c2) {
            this.c1 = c1;
            this.c2 = c2;
        }

        private Color interpolatedBg(double t) {
            int r, g, b;
            r = (int) (c1.getRed() * (1 - t) + c2.getRed() * t);
            g = (int) (c1.getGreen() * (1 - t) + c2.getGreen() * t);
            b = (int) (c1.getBlue() * (1 - t) + c2.getBlue() * t);
            System.out.format("%.2f %d %d %d\n", t, r, g, b);
            return new Color(r, g, b);
        }

        @Override
        protected Void doInBackground() throws Exception {
            double t = 0;
            double dt = 0.5;
            while (!isCancelled()) {
                t += dt;
                if (Double.compare(t, 1.0) >= 0) {
                    t = 1.0;
                    dt = -dt;
                } else if (Double.compare(t, 0.0) < 0) {
                    t = 0.0;
                    dt = -dt;
                }
                currBgColor =  interpolatedBg(t);
                repaint();
                try {
                    Thread.sleep(20);
                } catch (InterruptedException ie) {
                }
            }
            return null;
        }
    }

    public void setNormalBackground() {
        currBgColor = normalBg;
        repaint();
    }

    public void setMovable(boolean b) {
        movable = b;
    }

    public Actor.EActor getActor() {
        return actor;
    }

    public void setGamePieceListener(IGamePieceListener l) {
        listener = l;
    }

    public void mark() {
        if (!isMarked) {
            currBgColor = markBg;
            repaint();
            isMarked = true;
        }
    }

    public void unmark() {
        if (isMarked) {
            currBgColor = normalBg;
            repaint();
            isMarked = false;
        }
    }

    public boolean isMarked() {
        return isMarked;
    }

    /**
     * Sets whether the piece's name should be visible on the map (known),
     * or replaced by a '?' (not known).
     * @param b
     */
    public void setKnown(boolean b) {
        isKnown = b;
    }

    public void setPieceSelectionListener(IPieceSelectionListener pieceSelectionListener) {
        selectionListener = pieceSelectionListener;
    }

    public void remember() {
        lastPos = getLocation();
    }

    public void snapback() {
        if (lastPos != null) {
            setLocation(lastPos);
        }
    }

    void setSelectionMode(boolean b) {
        selectionMode = b;
        selectable = false;
    }

    /**
     * Sets a piece as selectable, for use with the piece selection mode
     * of PieceCollection.
     * @param b
     */
    void setSelectable(boolean b) {
        selectable = b;
    }

    @Override
    protected void paintComponent(Graphics g) {
        Dimension size = getSize();
        g.setColor(currBgColor);
        g.fillRect(0, 0, size.width, size.height);

        String s = text;
        if (!isKnown) {
            s = "?";
        }

        g.setColor(currTextColor);
        g.setFont(getFont());
        int tw = g.getFontMetrics().stringWidth(s);
        int tx = (getWidth() - tw) / 2;
        int ty = g.getFontMetrics().getHeight();
        g.drawString(s, tx, ty);

        if (selectable) {
            if (isHovered) {
                g.setColor(selectableColorHover);
            } else {
                g.setColor(selectableColor);
            }
            g.fillRect(0, 0, size.width, size.height);
        }
    }

    public void mouseClicked(MouseEvent e) {
        if (isKnown) {
            listener.pieceClicked(this, e.getClickCount() > 1, e.getModifiersEx());
        }
        listener.pieceReleased(this);
    }

    public void mousePressed(MouseEvent e) {
        if (selectionMode && selectable) {
            selectionListener.pieceSelected(actor);
        } else if (isKnown && !selectionMode) {
            mouseDown = true;
            clickPoint = e.getPoint();
            setBorder(clickBorder);
            currBgColor = clickBg;
            currTextColor = clickText;

            listener.pieceClicked(this, false, 0);
            repaint();
        }
    }

    public boolean isMovable() {
        return movable;
    }

    public void mouseReleased(MouseEvent e) {
        listener.pieceReleased(this);
        if (isKnown && !selectionMode) {
            mouseDown = false;
            setBorder(normalBorder);
            currBgColor = normalBg;

            currTextColor = normalText;
            if (wasDragged && movable) {
                Point pPanel = getLocation();
                Point pMouse = e.getPoint();
                pPanel.x += pMouse.x;
                pPanel.y += pMouse.y;
                listener.pieceDropped(this, pPanel);
                currBgColor = hoverBg;
            }
            repaint();
        }
    }

    public void mouseEntered(MouseEvent e) {
        isHovered = true;
        if (isKnown && !mouseDown && !selectionMode) {
//            currBgColor = hoverBg;
            currBgAnim = new BackgroundAnimator(normalBg, hoverBg);
            currBgAnim.execute();
        }

        repaint();
    }

    public void mouseExited(MouseEvent e) {
        isHovered = false;
        if (isKnown && !mouseDown && !selectionMode) {
            currBgAnim.cancel(true);
            currBgColor = normalBg;

        }
        repaint();
    }

    public void mouseDragged(MouseEvent e) {
        if (movable && !selectionMode) {
            Point loc = this.getLocation();
            loc.x += e.getX();
            loc.y += e.getY();

            wasDragged = true;

            Point p = e.getPoint();
            Point np = getLocation();

            int dx = p.x - clickPoint.x;
            int dy = p.y - clickPoint.y;
            np.x += dx;
            np.y += dy;
            setLocation(np);

            Point reportPoint = new Point(np);
            reportPoint.x += p.x;
            reportPoint.y += p.y;
            listener.pieceDragged(this, reportPoint);
        }
    }

    public void mouseMoved(MouseEvent e) {
        // do nothing
    }

    @Override
    public String toString() {
        return actor.toString() + ", loc: " + getLocation().toString() + ", vis: " + isVisible() + ", known: " + isKnown + ", resp: " + movable;
    }
}

class DarkPiece extends GamePiece {

    public DarkPiece(Actor.EActor who, String text) {
        super(who, text, Color.BLACK, new Color(96, 32, 32), Color.RED,
                Color.WHITE, Color.WHITE, Color.BLACK, Color.BLACK,
                new Color(127, 255, 127));
    }
}

class LightPiece extends GamePiece {

    public LightPiece(Actor.EActor who, String text) {
        super(who, text, Color.WHITE, new Color(200, 200, 255), Color.BLUE,
                Color.BLACK, Color.BLACK, Color.WHITE, Color.WHITE,
                new Color(127, 255, 127));
    }
}
